package errno

import (
	"encoding/json"
	"fmt"
	"log/slog"
)

type Exception struct {
	code   int
	desc   string
	fields map[string]any
}

var OK = Exception{code: 1}
var FAIL = Exception{code: 0}
var ERROR = Exception{code: -1}
var IOERR = Exception{code: -2}
var SYSERR = Exception{code: -3}
var NETERR = Exception{code: -4}
var TIMEOUT = Exception{code: -5}
var DATAERR = Exception{code: -6}
var SYSBUSY = Exception{code: -7}
var PARAMERR = Exception{code: -8}
var NOTFOUND = Exception{code: -9}
var NETCLOSE = Exception{code: -10}
var NETDELAY = Exception{code: -11}
var SENDFAIL = Exception{code: -12}
var RECVFAIL = Exception{code: -13}
var AUTHFAIL = Exception{code: -14}
var DAYLIMIT = Exception{code: -15}
var DUPLICATE = Exception{code: -16}
var UNINSTALL = Exception{code: -17}
var NOTCHANGED = Exception{code: -18}
var DETACHCONN = Exception{code: -999999}

func init() {
	OK.desc = GetErrorString(OK.code)
	FAIL.desc = GetErrorString(FAIL.code)
	ERROR.desc = GetErrorString(ERROR.code)
	IOERR.desc = GetErrorString(IOERR.code)
	SYSERR.desc = GetErrorString(SYSERR.code)
	NETERR.desc = GetErrorString(NETERR.code)
	TIMEOUT.desc = GetErrorString(TIMEOUT.code)
	DATAERR.desc = GetErrorString(DATAERR.code)
	SYSBUSY.desc = GetErrorString(SYSBUSY.code)
	PARAMERR.desc = GetErrorString(PARAMERR.code)
	NOTFOUND.desc = GetErrorString(NOTFOUND.code)
	NETCLOSE.desc = GetErrorString(NETCLOSE.code)
	NETDELAY.desc = GetErrorString(NETDELAY.code)
	SENDFAIL.desc = GetErrorString(SENDFAIL.code)
	RECVFAIL.desc = GetErrorString(RECVFAIL.code)
	AUTHFAIL.desc = GetErrorString(AUTHFAIL.code)
	DAYLIMIT.desc = GetErrorString(DAYLIMIT.code)
	DUPLICATE.desc = GetErrorString(DUPLICATE.code)
	UNINSTALL.desc = GetErrorString(UNINSTALL.code)
	NOTCHANGED.desc = GetErrorString(NOTCHANGED.code)
	DETACHCONN.desc = GetErrorString(DETACHCONN.code)
}

func (e Exception) Code() int {
	return e.code
}

func (e Exception) Error() string {
	return e.String()
}

func (e Exception) String() string {
	if e.desc == "" {
		return GetErrorString(e.code)
	} else {
		return e.desc
	}
}

func (e Exception) JsonBuffer() []byte {
	data := map[string]any{"code": e.code, "desc": e.desc}
	if len(e.fields) > 0 {
		data["extra"] = e.fields
	}
	return AssertNoError(json.Marshal(data))
}

func (e Exception) Fields() map[string]any {
	return e.fields
}

func (e Exception) Extra(fields map[string]any) Exception {
	res := Exception{e.code, e.String(), fields}
	return res
}

func AssertNil(err error) {
	if err != nil {
		panic(err)
	}
}

func Attr(err any) slog.Attr {
	return slog.Any("error", err)
}

func IgnoreError[T any](data T, err error) T {
	return data
}

func AssertNoError[T any](data T, err error) T {
	AssertNil(err)
	return data
}

func GetErrorString(code int) string {
	switch code {
	case FAIL.code:
		return "process failed"
	case SYSBUSY.code:
		return "system busy"
	case DATAERR.code:
		return "invalid data"
	case TIMEOUT.code:
		return "resource timeout"
	case PARAMERR.code:
		return "parameter error"
	case AUTHFAIL.code:
		return "permission denied"
	case DAYLIMIT.code:
		return "frequent operation"
	case NOTFOUND.code:
		return "resource not found"
	case DUPLICATE.code:
		return "resource duplicate"
	case NOTCHANGED.code:
		return "resource not changed"
	default:
		return "system error"
	}
}

func (err Exception) Panic(desc ...any) {
	if msg := fmt.Sprint(desc...); msg == "" {
		panic(err)
	} else {
		panic(Exception{err.code, msg, err.fields})
	}
}

func (err Exception) Copy(desc ...any) Exception {
	if msg := fmt.Sprint(desc...); msg == "" {
		return Exception{err.code, err.desc, err.fields}
	} else {
		return Exception{err.code, msg, err.fields}
	}
}
